Passed
Push — feature/applicant-profile-skil... ( 4f71a7...78e4f0 )
by Tristan
05:47 queued 11s
created

queries.ts ➔ addOrRemove   A

Complexity

Conditions 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 11
rs 10
cc 2
1
import { localizedField, localizedFieldNonNull } from "../models/app";
2
import { localizeFieldNonNull, localizeField, Locales } from "./localize";
3
4
/**
5
 * Shortcut function that returns the id attribute of an object.
6
 * @param item
7
 */
8
export function getId<T extends { id: number }>(item: T): number {
9
  return item.id;
10
}
11
12
export function identity<T>(value: T): T {
13
  return value;
14
}
15
16
/**
17
 * Returns true if value is not null or undefined.
18
 * Can be used to filter nulls and undefined values out of an array.
19
 * @param item
20
 */
21
export function notEmpty<T>(value: T | null | undefined): value is T {
22
  return value !== null && value !== undefined;
23
}
24
25
export function stringNotEmpty(value: string | null): value is string {
26
  return notEmpty(value) && value.length > 0;
27
}
28
29
/**
30
 * Returns true if value id null OR undefined.
31
 * @param item
32
 */
33
export function empty<T>(
34
  value: T | null | undefined,
35
): value is null | undefined {
36
  return value === null || value === undefined;
37
}
38
39
/**
40
 * From an array of objects, return the first object with a specific id value.
41
 * @param objs
42
 * @param id
43
 */
44
export function find<T extends { id: number }>(
45
  objs: T[],
46
  id: number,
47
): T | null {
48
  const found = objs.filter((item) => item.id === id);
49
  return found.length > 0 ? found[0] : null;
50
}
51
52
/**
53
 * Return all objects from array with the specified key-value attribute.
54
 * @param objs
55
 * @param prop
56
 * @param value
57
 */
58
export function where<T, K extends keyof T>(
59
  objs: T[],
60
  prop: K,
61
  value: any,
62
): T[] {
63
  return objs.filter((obj) => prop in obj && obj[prop] === value);
64
}
65
66
/**
67
 * Return first object from array with the specified key-value attribute.
68
 * @param objs
69
 * @param prop
70
 * @param value
71
 */
72
export function whereFirst<T, K extends keyof T>(
73
  objs: T[],
74
  prop: K,
75
  value: any,
76
): T {
77
  return where(objs, prop, value)[0];
78
}
79
80
/**
81
 * Map each key-value attribute of an object into an array
82
 * @param object
83
 * @param mapFn
84
 */
85
export function objectMap<A, B>(
86
  object: {
87
    [key: string]: A;
88
  },
89
  mapFn: (key: string, value: A) => B,
90
): B[] {
91
  return Object.keys(object).reduce((result: B[], key: string): B[] => {
92
    result.push(mapFn(key, object[key]));
93
    return result;
94
  }, []);
95
}
96
97
/**
98
 * Return a copy of an object, with each value being passed through a map function.
99
 * Each value's key is unaffected.
100
 * @param object
101
 * @param mapFn
102
 */
103
export function mapObjectValues<A, B>(
104
  object: IndexedObject<A>,
105
  mapFn: (value: A) => B,
106
): IndexedObject<B> {
107
  return Object.entries(object).reduce(
108
    (result: IndexedObject<B>, [key, value]) => {
109
      result[key] = mapFn(value);
110
      return result;
111
    },
112
    {},
113
  );
114
}
115
116
type IndexedObject<T> = {
117
  [key: string]: T;
118
};
119
120
/**
121
 * Maps an array of items into an object, with each transformed into an attribute
122
 * @param items array of objects
123
 * @param keyFn Function that returns a unique key for each object. Typically the getId function.
124
 * @param valFn Function that returns a new value for each object.
125
 */
126
export function mapToObjectTrans<A, B>(
127
  items: A[],
128
  keyFn: (item: A) => string | number,
129
  valFn: (item: A) => B,
130
): IndexedObject<B> {
131
  return items.reduce((result: IndexedObject<B>, item: A): IndexedObject<B> => {
132
    const key = keyFn(item);
133
    result[key] = valFn(item);
134
    return result;
135
  }, {});
136
}
137
138
/**
139
 * Maps an array of items into an object, with each item set as an attribute.
140
 * @param items array of objects
141
 * @param keyFn Function that returns a unique key for each object. Typically the getId function.
142
 */
143
export function mapToObject<T>(
144
  items: T[],
145
  keyFn: (item: T) => string | number,
146
): IndexedObject<T> {
147
  return mapToObjectTrans(items, keyFn, (item): T => item);
148
}
149
150
/**
151
 * Checks if an object has an attribute with a particular key
152
 * @param object
153
 * @param key
154
 */
155
export function hasKey<T>(
156
  object: { [key: string]: T },
157
  key: string | number,
158
): boolean {
159
  return object[key] !== undefined;
160
}
161
162
/**
163
 * Returns the value at the specified key. If the key is not present, throws an error.
164
 * @param object
165
 * @param key
166
 * @param errorMessage
167
 */
168
export function getOrThrowError<T>(
169
  object: { [key: string]: T },
170
  key: string | number,
171
  errorMessage: string,
172
): T {
173
  if (!hasKey(object, key)) {
174
    throw new Error(errorMessage);
175
  }
176
  return object[key];
177
}
178
179
/** Return a copy of the object with specific property removed */
180
export function deleteProperty<T, K extends keyof T>(
181
  obj: T,
182
  key: K,
183
): Omit<T, K> {
184
  const { [key]: _, ...newObj } = obj;
185
  return newObj;
186
}
187
188
/**
189
 * Iterate through the properties of the object, checking which return true for the filterFunction.
190
 * Return a copy of the object, without the properties that don't pass the filter.
191
 */
192
export function filterObjectProps<T>(
193
  obj: IndexedObject<T>,
194
  filter: (value: T) => boolean,
195
): IndexedObject<T> {
196
  return Object.entries(obj).reduce(
197
    (newObj: IndexedObject<T>, [key, value]): IndexedObject<T> => {
198
      if (filter(value)) {
199
        newObj[key] = value;
200
      }
201
      return newObj;
202
    },
203
    {},
204
  );
205
}
206
207
/** Return a copy of the list with duplicate elements removed */
208
export function uniq<T>(x: T[]): T[] {
209
  return Array.from(new Set(x));
210
}
211
212
export function flatten<T>(x: T[][]): T[] {
213
  const concat = (ls: T[], xs: T[]): T[] => ls.concat(xs);
214
  return x.reduce(concat, []);
215
}
216
217
export function removeDuplicatesById<T extends { id: number }>(
218
  items: T[],
219
): T[] {
220
  interface Accumulator {
221
    contents: T[];
222
    ids: number[];
223
  }
224
  const reducer = (result: Accumulator, next: T): Accumulator => {
225
    if (!result.ids.includes(next.id)) {
226
      result.contents.push(next);
227
      result.ids.push(next.id);
228
    }
229
    return result;
230
  };
231
  return items.reduce(reducer, { contents: [], ids: [] }).contents;
232
}
233
234
/**
235
 * Wrapper function to be passed to Array.sort() for objects with
236
 * a "name" property of type localizedField or localizedFieldNonNull.
237
 *
238
 * @param locale
239
 */
240
export function sortLocalizedAlphabetical(locale: Locales) {
241
  return (
242
    first: { name: localizedField | localizedFieldNonNull },
243
    second: { name: localizedField | localizedFieldNonNull },
244
  ): number => {
245
    let firstName: string | null | undefined;
246
    let secondName: string | null | undefined;
247
248
    if (first.name.en !== null && first.name.fr !== null) {
249
      firstName = localizeFieldNonNull(
250
        locale,
251
        first as { name: localizedFieldNonNull },
252
        "name",
253
      ).toLocaleUpperCase();
254
    } else {
255
      firstName = localizeField(locale, first, "name")?.toLocaleUpperCase();
256
    }
257
258
    if (second.name.en !== null && second.name.fr !== null) {
259
      secondName = localizeFieldNonNull(
260
        locale,
261
        second as { name: localizedFieldNonNull },
262
        "name",
263
      ).toLocaleUpperCase();
264
    } else {
265
      secondName = localizeField(locale, second, "name")?.toLocaleUpperCase();
266
    }
267
268
    if (
269
      firstName !== null &&
270
      firstName !== undefined &&
271
      secondName !== null &&
272
      secondName !== undefined
273
    ) {
274
      if (firstName < secondName) {
275
        return -1;
276
      }
277
278
      if (firstName > secondName) {
279
        return 1;
280
      }
281
    }
282
283
    return 0;
284
  };
285
}
286
287
/*
288
 * Decrement the number if it above zero, else return 0.
289
 * This helps to avoid some pathological edge cases where pendingCount becomes permanently bugged.
290
 * @param num
291
 */
292
export function decrement(num: number): number {
293
  return num <= 0 ? 0 : num - 1;
294
}
295
296
/**
297
 * If the element already exists in the array, then remove it, and return array.
298
 * Otherwise, if the element does not exist in the array, add it to the array, and return new array.
299
 * @param element
300
 * @param array
301
 */
302
export function addOrRemove<T>(element: T, array: T[]): T[] {
303
  return array.includes(element)
304
    ? array.filter((T) => T !== element)
305
    : [...array, element];
306
}
307